/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Image Conversion routine implementations.
 * \author Antti Maula <antti.maula@tkk.fi>
 */

#include "ImageConversion.hpp"
#ifndef DISABLE_MACI_IMAGE_IMAGECONTAINER_EXTERNAL_CONVERSIONS
#include <png.h>

#include "JPEGDecoder.hpp"
#include "JPEGEncoder.hpp"

#include "PNGDecoder.hpp"
#include "PNGEncoder.hpp"

#include "YUYV_to_RGB.h"

#else
# warning "ImageContainer External Conversions Disabled - No external encodings/decodings available"
#endif
#include <assert.h>

//*****************************************************************************
//*****************************************************************************
using namespace MaCI::Image;
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

CImageConversion::CImageConversion()
  : iTemporary_buffer(NULL),
    iTemporary_buffer_size(0)
{
}
//*****************************************************************************

CImageConversion::~CImageConversion()
{
  // Clear temporary buffer.
  if (iTemporary_buffer != NULL) {
    delete[] iTemporary_buffer;
    iTemporary_buffer = NULL;
    iTemporary_buffer_size = 0;
  }
}
//*****************************************************************************

CImageConversion::CImageConversion(const CImageConversion &aSrc)
  : iTemporary_buffer(NULL),
    iTemporary_buffer_size(0)
{
  *this = aSrc;
}
//*****************************************************************************

CImageConversion &CImageConversion::operator=(const CImageConversion &aSrc)
{
  // Just mark the new object as empty. (Will be reallocated on
  // demand)
  iTemporary_buffer = NULL;
  iTemporary_buffer_size = 0;
  return *this;
}
//*****************************************************************************

void CImageConversion::ReserveTemporary(const unsigned int aBytes)
{
  // Check whether the buffer is not defined, or is smaller than required.
  if (iTemporary_buffer == NULL || iTemporary_buffer_size < aBytes) {
    // store the required amount.
    iTemporary_buffer_size = aBytes;

    // If the buffer already exists, delete it.
    if (iTemporary_buffer != NULL) delete[] iTemporary_buffer;

    // create new temporary buffer.
    iTemporary_buffer = new unsigned char [iTemporary_buffer_size];
  }
}
//*****************************************************************************

bool CImageConversion::ConvertImageData(const EImageDataType aSrcType, 
                                       const EImageDataType aDestType,
                                       unsigned int &aWidth, 
                                       unsigned int &aHeight,
                                       const unsigned char *aInputPtr, 
                                       const unsigned int aInputSize,
                                       unsigned char **aOutputPtrPtr,
                                       unsigned int *aOutputSizePtr)
{
  bool result = false;

  // \todo This could be easily converted to 2-dimensional array of
  // function pointers.

  if (aSrcType == KImageDataJPEG) {
    // Source is JPEG
    if (aDestType == KImageDataRGB) {
      // Target is RGB
      result = ConvertImageData_JPEG_to_RGB(aWidth, aHeight,
                                            aInputPtr, 
                                            aInputSize,
                                            aOutputPtrPtr,
                                            aOutputSizePtr);
    }
    
  } else if (aSrcType == KImageDataRGB) {
    // Source is RGB
    if (aDestType == KImageDataJPEG) {
      // Target is JPEG
      result = ConvertImageData_RGB_to_JPEG(aWidth, aHeight,
                                            aInputPtr, 
                                            aInputSize,
                                            aOutputPtrPtr,
                                            aOutputSizePtr);
      
    } else if (aDestType == KImageDataPNG) {
      // Target is PNG
      result = ConvertImageData_RGB_to_PNG(aWidth, aHeight,
                                           aInputPtr, 
                                           aInputSize,
                                           aOutputPtrPtr,
                                           aOutputSizePtr);
    
    }
    
  } else if (aSrcType == KImageDataGreyscale) {
    // Source is Greyscale
    if (aDestType == KImageDataJPEG) {
      // Target is JPEG
      result = ConvertImageData_Greyscale_to_JPEG(aWidth, aHeight,
                                                  aInputPtr, 
                                                  aInputSize,
                                                  aOutputPtrPtr,
                                                  aOutputSizePtr);

    }
    
  } else if (aSrcType == KImageDataYUV420P) {
    // Source is YUV420P
    if (aDestType == KImageDataJPEG) {
      // Target is JPEG
      result = ConvertImageData_YUV420P_to_JPEG(aWidth, aHeight,
                                                aInputPtr, 
                                                aInputSize,
                                                aOutputPtrPtr,
                                                aOutputSizePtr);
    }
  } else if (aSrcType == KImageDataYUV422) {
    // Source is YUV422
    if (aDestType == KImageDataJPEG) {
      // Target is JPEG
      result = ConvertImageData_YUV422_to_JPEG(aWidth, aHeight,
                                               aInputPtr, 
                                               aInputSize,
                                               aOutputPtrPtr,
                                               aOutputSizePtr);
    } else if (aDestType == KImageDataRGB) {
      // Target is RGB
      result = ConvertImageData_YUV422_to_RGB(aWidth, aHeight,
                                               aInputPtr, 
                                               aInputSize,
                                               aOutputPtrPtr,
                                               aOutputSizePtr);
    }
  } else if (aSrcType == KImageDataPNG) {
    // Source is PNG
    if (aDestType == KImageDataRGB) {
      result = ConvertImageData_PNG_to_RGB(aWidth, aHeight,
                                           aInputPtr, 
                                           aInputSize,
                                           aOutputPtrPtr,
                                           aOutputSizePtr);
      
    }
  } else if (aSrcType == KImageDataYUYV) {
    // Source is PNG
    if (aDestType == KImageDataJPEG) {
      result = ConvertImageData_YUYV_to_JPEG(aWidth, aHeight,
                                             aInputPtr, 
                                             aInputSize,
                                             aOutputPtrPtr,
                                             aOutputSizePtr);
      
    }
  }

  return result;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

bool 
CImageConversion::ConvertImageData_JPEG_to_RGB(unsigned int &aWidth, 
                                               unsigned int &aHeight,
                                               const unsigned char *aInputPtr, 
                                               const unsigned int aInputSize,
                                               unsigned char **aOutputPtrPtr,
                                               unsigned int *aOutputSizePtr)
{
  bool result = false;

#ifndef DISABLE_MACI_IMAGE_IMAGECONTAINER_EXTERNAL_CONVERSIONS
  
  // Init Decoder.
  CJPEGDecoder dec(reinterpret_cast<const char *>(aInputPtr), aInputSize);
  
  // Initialize DYNAMIC storage.
  const int ds = dec.GetRGBDataSize();
  if (ds > 0) {
    *aOutputSizePtr = ds;
    *aOutputPtrPtr = new unsigned char [ *aOutputSizePtr ];
    
    // Decode!
    result = dec.GetRGBDataCopy(reinterpret_cast<char *>(*aOutputPtrPtr), *aOutputSizePtr);

    // If succesfull, check whether image sizes match.
    const unsigned int iw = dec.GetImageWidth();
    const unsigned int ih = dec.GetImageHeight();
    if (iw != aWidth) aWidth = iw;
    if (ih != aHeight) aHeight = ih;
  }
#endif

  // return result.
  return result;
}
//*****************************************************************************

bool 
CImageConversion::ConvertImageData_RGB_to_JPEG(const unsigned int aWidth, 
                                              const unsigned int aHeight,
                                              const unsigned char *aInputPtr, 
                                              const unsigned int aInputSize,
                                              unsigned char **aOutputPtrPtr,
                                              unsigned int *aOutputSizePtr)
{
  bool result = false;

#ifndef DISABLE_MACI_IMAGE_IMAGECONTAINER_EXTERNAL_CONVERSIONS
  dPrint(10,"Attempting RGB -> JPEG conversion");

  assert(( aWidth * aHeight * 3 ) <= aInputSize);
  CJPEGEncoder enc;
  enc.SetRGBDataPtr(reinterpret_cast<const char *>(aInputPtr), aWidth, aHeight); 

  // Initialize DYNAMIC storage.
  const int ds = enc.GetJPEGDataSize();
  if (ds >= 0) {
    *aOutputSizePtr = ds;
    *aOutputPtrPtr = new unsigned char [ *aOutputSizePtr ]; 
    
    // Encode!
    result = enc.GetJPEGDataCopy(reinterpret_cast<char *>(*aOutputPtrPtr), *aOutputSizePtr);
  }
#endif
  
  return result;
}
//*****************************************************************************

bool 
CImageConversion::ConvertImageData_RGB_to_PNG(const unsigned int aWidth, 
                                              const unsigned int aHeight,
                                              const unsigned char *aInputPtr, 
                                              const unsigned int aInputSize,
                                              unsigned char **aOutputPtrPtr,
                                              unsigned int *aOutputSizePtr)
{
  bool result = false;

#ifndef DISABLE_MACI_IMAGE_IMAGECONTAINER_EXTERNAL_CONVERSIONS
  assert(( aWidth * aHeight * 3 ) <= aInputSize);
 
  CPNGEncoder enc;
  enc.SetRGBDataPtr(reinterpret_cast<const char *>(aInputPtr), aInputSize,
                    aWidth, aHeight); 
  
  if (enc.Encode()) {
    // Initialize DYNAMIC storage.
    const int ds = enc.GetPNGDataSize();
    if (ds >= 0) {
      *aOutputSizePtr = ds;
      *aOutputPtrPtr = new unsigned char [ *aOutputSizePtr ]; 
      
      // Encode!
      result = enc.GetPNGDataCopy(reinterpret_cast<char *>(*aOutputPtrPtr), *aOutputSizePtr);
    }
  }
#endif
  
  return result;
}
//*****************************************************************************

bool 
CImageConversion::ConvertImageData_PNG_to_RGB(unsigned int &aWidth, 
                                              unsigned int &aHeight,
                                              const unsigned char *aInputPtr, 
                                              const unsigned int aInputSize,
                                              unsigned char **aOutputPtrPtr,
                                              unsigned int *aOutputSizePtr)
{
  bool result = false;
#ifndef DISABLE_MACI_IMAGE_IMAGECONTAINER_EXTERNAL_CONVERSIONS
  // Init Decoder.
  CPNGDecoder dec(reinterpret_cast<const char *>(aInputPtr), aInputSize);
  
  // Decode!
  if (dec.Decode()) {
    
    // Initialize DYNAMIC storage.
    const int ds = dec.GetRGBDataSize();
    if (ds > 0) {
      *aOutputSizePtr = ds;
      *aOutputPtrPtr = new unsigned char [ *aOutputSizePtr ];
      
      // Decode!
      result = dec.GetRGBDataCopy(reinterpret_cast<char *>(*aOutputPtrPtr), *aOutputSizePtr);
      
      // If succesfull, check whether image sizes match.
      if (result) {
        const unsigned int iw = dec.GetImageWidth();
        const unsigned int ih = dec.GetImageHeight();
        if (iw != aWidth) aWidth = iw;
        if (ih != aHeight) aHeight = ih;
      }
    }
  }
#endif
    return result;
}
//*****************************************************************************

bool 
CImageConversion::ConvertImageData_Greyscale_to_JPEG(const unsigned int aWidth, 
                                                    const unsigned int aHeight,
                                                    const unsigned char *aInputPtr, 
                                                    const unsigned int aInputSize,
                                                    unsigned char **aOutputPtrPtr,
                                                    unsigned int *aOutputSizePtr)
{
  bool result = false;

#ifndef DISABLE_MACI_IMAGE_IMAGECONTAINER_EXTERNAL_CONVERSIONS
  dPrint(10,"Attempting Greyscale -> JPEG conversion");

  //assert(( aWidth * aHeight * 3 ) <= aInputSize);
  CJPEGEncoder enc;
  enc.SetGrayscaleDataPtr(reinterpret_cast<const char *>(aInputPtr), aWidth, aHeight); 
  
  // Initialize DYNAMIC storage.
  const int ds = enc.GetJPEGDataSize();
  if (ds >= 0) {
    *aOutputSizePtr = ds;
    *aOutputPtrPtr = new unsigned char [ *aOutputSizePtr ]; 
    
    // Encode!
    result = enc.GetJPEGDataCopy(reinterpret_cast<char *>(*aOutputPtrPtr), *aOutputSizePtr);
  }
  
#endif

  return result;
}
//*****************************************************************************

bool 
CImageConversion::ConvertImageData_YUV420P_to_JPEG(const unsigned int aWidth, 
                                                 const unsigned int aHeight,
                                                 const unsigned char *aInputPtr, 
                                                 const unsigned int aInputSize,
                                                 unsigned char **aOutputPtrPtr,
                                                 unsigned int *aOutputSizePtr)
{
  bool result = false;

#ifndef DISABLE_MACI_IMAGE_IMAGECONTAINER_EXTERNAL_CONVERSIONS
  dPrint(10,"Attempting YUV420P -> JPEG conversion");
  
  // The real size of YUV420P = 1.5*aWidthPixels * aHeightPixels
  assert( ( (aWidth + (aWidth>>1)) * aHeight ) <= aInputSize);
  CJPEGEncoder enc;
  enc.SetYUVDataPtr(reinterpret_cast<const char *>(aInputPtr), aWidth, aHeight); 

  // Initialize DYNAMIC storage.
  const int ds = enc.GetJPEGDataSize();
  if (ds >= 0) {
    *aOutputSizePtr = ds;
    *aOutputPtrPtr = new unsigned char [ *aOutputSizePtr ]; 
    
    // Encode!
    result = enc.GetJPEGDataCopy(reinterpret_cast<char *>(*aOutputPtrPtr), *aOutputSizePtr);
  }

#endif

  return result;
}
//*****************************************************************************

bool 
CImageConversion::ConvertImageData_YUV422_to_JPEG(const unsigned int aWidth, 
                                                 const unsigned int aHeight,
                                                 const unsigned char *aInputPtr, 
                                                 const unsigned int aInputSize,
                                                 unsigned char **aOutputPtrPtr,
                                                 unsigned int *aOutputSizePtr)
{
  bool result = false;

#ifndef DISABLE_MACI_IMAGE_IMAGECONTAINER_EXTERNAL_CONVERSIONS
  dPrint(10,"Attempting YUV422 -> JPEG conversion");
  
  // The real size of YUV422 = 2 * aWidthPixels * aHeightPixels
  assert( 2 * aWidth * aHeight <= aInputSize);
  
  // Reserve temporary space.
  ReserveTemporary( (aWidth + (aWidth>>1)) * aHeight);
  
  // Check that buffer is really there.
  assert(iTemporary_buffer != NULL);
  
  
  //////////////////////// This code ripped from 'utils/imageutils.c' /////////
  // Convert 422 -> 420P
  // (char *YUV, char *YUV2, int M, int N) {
  const char *YUV = (const char *)aInputPtr;
  char *YUV2 = (char*)iTemporary_buffer;
  const int M = aWidth;
  const int N = aHeight;
  int i, j,iM,iM4;
  int y0, y1, u, v;
  char *Y, *U, *V;
  
  Y=YUV2;
  U=YUV2+M*N;
  V=YUV2+M*N+M*N/4;
  for (i = 0; i < N; i+=2) {
    iM=i*M;
    iM4=iM/4;
    for (j = 0; j < M; j+=2) {
      u = (unsigned char) YUV[(iM+j)*2+0];
      y0 = (unsigned char) YUV[(iM+j)*2 + 1];
      v = (unsigned char) YUV[(iM+j)*2 + 2];
      y1 = (unsigned char) YUV[(iM+j)*2 + 3];
      Y[iM+j+0]=y0;
      Y[iM+j+1]=y1;
      U[iM4+j/2]=u;
      V[iM4+j/2]=v;
    }
    
    for (; j < 2*M; j+=2) {
      u = (unsigned char) YUV[(iM+j)*2+0];
      y0 = (unsigned char) YUV[(iM+j)*2 + 1];
      v = (unsigned char) YUV[(iM+j)*2 + 2];
      y1 = (unsigned char) YUV[(iM+j)*2 + 3];
      Y[iM+j+0]=y0;
      Y[iM+j+1]=y1;
    }	
  }
  /////////////////////////////////////////////////////////////////////////////

  
  CJPEGEncoder enc;
  enc.SetYUV420PDataPtr(YUV2, M, N);
  
  // Initialize DYNAMIC storage.
  const int ds = enc.GetJPEGDataSize();
  if (ds >= 0) {
    *aOutputSizePtr = ds;
    *aOutputPtrPtr = new unsigned char [ *aOutputSizePtr ]; 
    
    // Encode!
    result = enc.GetJPEGDataCopy(reinterpret_cast<char *>(*aOutputPtrPtr), *aOutputSizePtr);
  }
#endif

  return result;
}

//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

bool 
CImageConversion::ConvertImageData_YUV422_to_RGB(const unsigned int aWidth, 
                                                 const unsigned int aHeight,
                                                 const unsigned char *aInputPtr, 
                                                 const unsigned int aInputSize,
                                                 unsigned char **aOutputPtrPtr,
                                                 unsigned int *aOutputSizePtr)
{
  bool result = false;

  

  
#ifndef DISABLE_MACI_IMAGE_IMAGECONTAINER_EXTERNAL_CONVERSIONS
  
  result = true;

  dPrint(10,"Attempting YUV422 -> RGB conversion");

  // The real size of YUV422 = 2 * aWidthPixels * aHeightPixels
  assert( 2 * aWidth * aHeight <= aInputSize);

  //We create a pointer to the input data
  const char *YUV = (const char *)aInputPtr;
  
   //We set the output size to be 3 x width x height
  *aOutputSizePtr = 3 *aWidth * aHeight;

  
   //We reserve space for the output data
  *aOutputPtrPtr = new unsigned char [ *aOutputSizePtr ]; 
 

  
  //The algorithm uses char *RGB to output its data,
  // So we create a char *RGB that points to the output
   char *RGB = reinterpret_cast<char *>( *aOutputPtrPtr);
 

  //The algorithm also uses NumPixels, which we set here
  int NumPixels = aWidth * aHeight;

 

  //////////////////////////////////////////////////////////////////////
  //Grabbed from imageutils.c

#define YUV2RGB(y, u, v, r, g, b)\
  r = y + ((v * 1434) / 2048);\
  g = y - ((u * 406) / 2048) - ((v * 595) / 2048);\
  b = y + ((u * 2078) / 2048);\
  r = r < 0 ? 0 : r;\
  g = g < 0 ? 0 : g;\
  b = b < 0 ? 0 : b;\
  r = r > 255 ? 255 : r;\
  g = g > 255 ? 255 : g;\
  b = b > 255 ? 255 : b

  /*routine to convert an array of YUV data to RGB format
    from Bart Nabbe*/

  int i, j;
  register int y0, y1, u, v;
  register int r, g, b;
  
  for (i = 0, j = 0; i < 2 * NumPixels; i += 4, j += 6)
    {
      u = (unsigned char) YUV[i + 0] - 128;
      y0 = (unsigned char) YUV[i + 1];
      v = (unsigned char) YUV[i + 2] - 128;
      y1 = (unsigned char) YUV[i + 3];
      YUV2RGB (y0, u, v, r, g, b);
      RGB[j + 0] = r;
      RGB[j + 1] = g;
      RGB[j + 2] = b;
      YUV2RGB (y1, u, v, r, g, b);
      RGB[j + 3] = r;
      RGB[j + 4] = g;
      RGB[j + 5] = b;
    }
    //////////////////////////////////////////////////////////////////////

  
#endif


  return result;

}
//*****************************************************************************

bool 
CImageConversion::ConvertImageData_YUYV_to_JPEG(const unsigned int aWidth, 
                                                const unsigned int aHeight,
                                                const unsigned char *aInputPtr, 
                                                const unsigned int aInputSize,
                                                unsigned char **aOutputPtrPtr,
                                                unsigned int *aOutputSizePtr)
{
  bool result = false;
  
#ifndef DISABLE_MACI_IMAGE_IMAGECONTAINER_EXTERNAL_CONVERSIONS
  
  result = true;
  
  dPrint(10,"Attempting YUYV -> JPEG conversion");
  
  // The real size of YUYV= 2 * aWidthPixels * aHeightPixels
  assert( 2 * aWidth * aHeight <= aInputSize);
  
  // Reserve temporary space.
  ReserveTemporary(3 * aWidth * aHeight);

  uint8_t *optr = GetTemporaryBufferPtr();


  // Call Conversion routine
  conversion_YUYV_to_RGB(optr, aInputPtr,
                         aWidth, aHeight);

  
  // Now the data in temporary buffer is in RGB format; Convert it to JPEG.
  CJPEGEncoder enc;
  enc.SetRGBDataPtr(reinterpret_cast<const char *>(GetTemporaryBufferPtr()), aWidth, aHeight); 
  
  // Initialize DYNAMIC storage.
  const int ds = enc.GetJPEGDataSize();
  if (ds >= 0) {
    *aOutputSizePtr = ds;
    *aOutputPtrPtr = new unsigned char [ *aOutputSizePtr ]; 
    
    // Encode!
    result = enc.GetJPEGDataCopy(reinterpret_cast<char *>(*aOutputPtrPtr), *aOutputSizePtr);
  }
  
#endif


  return result;

}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
